package drds.connection_pool;

import drds.connection_pool.proxy.ConnectionProxy;
import drds.connection_pool.util.FastList;
import drds.connection_pool.util.IConcurrentBagEntry;
import drds.connection_pool.util.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Entry used in the ConcurrentBag to track Connection instances.
 */
public final class PoolEntry implements IConcurrentBagEntry
{
   private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
   private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater;

   static
   {
      stateUpdater = AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
   }

   private final FastList<Statement> openStatements;
   private final Pool hikariPool;
   private final boolean isReadOnly;
   private final boolean isAutoCommit;
   Connection connection;
   long lastAccessed;
   long lastBorrowed;
   @SuppressWarnings("FieldCanBeLocal")
   private volatile int state = 0;
   private volatile boolean evict;
   private volatile ScheduledFuture<?> endOfLife;

   PoolEntry(final Connection connection, final AbstractPool abstractPool, final boolean isReadOnly, final boolean isAutoCommit)
   {
      this.connection = connection;
      this.hikariPool = (Pool) abstractPool;
      this.isReadOnly = isReadOnly;
      this.isAutoCommit = isAutoCommit;
      this.lastAccessed = Clock.currentTime();
      this.openStatements = new FastList<Statement>(Statement.class, 16);
   }

   /**
    * Release this entry back to the proxy.
    *
    * @param lastAccessed last access time-stamp
    */
   public void recycle(final long lastAccessed)
   {
      if (connection != null)
      {
         this.lastAccessed = lastAccessed;
         hikariPool.recycle(this);
      }
   }

   /**
    * Set the end of life {@link ScheduledFuture}.
    *
    * @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
    */
   void setFutureEol(final ScheduledFuture<?> endOfLife)
   {
      this.endOfLife = endOfLife;
   }

   Connection createProxyConnection(final ProxyLeakRunnable leakTask, final long now)
   {
      return ProxyFactory.getConnectionProxy(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
   }

   public void resetConnectionState(final ConnectionProxy connectionProxy, final int dirtyBits) throws SQLException
   {
      hikariPool.resetConnectionState(connection, connectionProxy, dirtyBits);
   }

   public String getPoolName()
   {
      return hikariPool.toString();
   }

   public boolean isMarkedEvicted()
   {
      return evict;
   }

   void markEvicted()
   {
      this.evict = true;
   }

   public void evict(final String closureReason)
   {
      hikariPool.closeConnection(this, closureReason);
   }

   /**
    * Returns millis since lastBorrowed
    */
   long getMillisSinceBorrowed()
   {
      return Clock.elapsedMillis(lastBorrowed);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public String toString()
   {
      final long now = Clock.currentTime();
      return connection
         + ", accessed " + Clock.elapsedDisplayString(lastAccessed, now) + " ago, "
         + stateToString();
   }

   // ***********************************************************************
   //                      IConcurrentBagEntry methods
   // ***********************************************************************

   /**
    * {@inheritDoc}
    */
   @Override
   public int getState()
   {
      return stateUpdater.get(this);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void setState(int update)
   {
      stateUpdater.set(this, update);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public boolean compareAndSet(int expect, int update)
   {
      return stateUpdater.compareAndSet(this, expect, update);
   }

   Connection close()
   {
      ScheduledFuture<?> eol = endOfLife;
      if (eol != null && !eol.isDone() && !eol.cancel(false))
      {
         LOGGER.warn("{} - maxLifeTime expiration task cancellation unexpectedly returned false for connectionProxy {}", getPoolName(), connection);
      }

      Connection connection = this.connection;
      this.connection = null;
      endOfLife = null;
      return connection;
   }

   private String stateToString()
   {
      switch (state)
      {
         case STATE_IN_USE:
            return "IN_USE";
         case STATE_NOT_IN_USE:
            return "NOT_IN_USE";
         case STATE_REMOVED:
            return "REMOVED";
         case STATE_RESERVED:
            return "RESERVED";
         default:
            return "Invalid";
      }
   }
}
